﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using Newtonsoft.Json;
using CSRedis;

#if NETSTANDARD2_0 || NETSTANDARD2_1

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

#else

using System.Configuration;

#endif

namespace HCenter.CacheHelper
{
    public class CSRedisCacheImp : ICacheProvider
    {
        #region var
        private CSRedisClient cache;
        /// <summary>
        /// 数据库
        /// </summary>
        private int DbIndex { get; }
        private string DBName { get; }
        public string KeySuffix { get; set; }
        /// <summary>
        /// redis 连接字符串
        /// 27.0.0.1:6379,192.168.80.130:6379,allowadmin=true,password=123456
        /// </summary>
        public string RedisConnectionString { get; set; }
        private static readonly ConcurrentDictionary<string, Lazy<ObjectPool<CSRedisClient>>> _pool =
            new ConcurrentDictionary<string, Lazy<ObjectPool<CSRedisClient>>>();
        #endregion

        #region CSRedisCacheImp
        public CSRedisCacheImp() : this(null, 0)
        {
        }
        public CSRedisCacheImp(string dbname, int dbIndex) : this(dbname, dbIndex, null)
        {
        }
        public CSRedisCacheImp(string redisConnectionString) : this(null, 0, redisConnectionString)
        {
        }
        public CSRedisCacheImp(string dbname, int dbIndex, string redisConnectionString)
        {
            if (dbname.IsNullOrEmpty())
                dbname = this.GetType().Name;
            DBName = dbname;
            if (dbIndex < 0)
                dbIndex = 0;
            this.DbIndex = dbIndex;

#if NETSTANDARD2_0 || NETSTANDARD2_1

            if(redisConnectionString.IsNullOrEmpty())
            {
                string _configPath = System.IO.Path.Combine(AppContext.BaseDirectory, "appsettings.json");
                IConfiguration s_ConfigJson = LoadJsonFile(_configPath);
                redisConnectionString = s_ConfigJson["RedisExchangeHosts"];
            }
            RedisConnectionString = redisConnectionString;

#else

            RedisConnectionString = redisConnectionString ?? ConfigurationManager.ConnectionStrings["RedisExchangeHosts"].ConnectionString;

#endif
            //connection = GetManager(redisConnectionString);
            //cache = connection.GetDatabase();
            cache = GetRedisClient(RedisConnectionString);
        }

#if NETSTANDARD2_0 || NETSTANDARD2_1
        private static IConfiguration LoadJsonFile(string FilePath)
        {
            var s_ConfigJson = new ConfigurationBuilder()
                .SetBasePath(System.AppContext.BaseDirectory)
                .AddJsonFile(FilePath, optional: false, reloadOnChange: true)
                .Build();
            return s_ConfigJson;
        }
#endif
        #endregion

        #region Set/Get
        /// <summary>
        /// 保存单个key value
        /// </summary>
        /// <param name="key">Redis Key</param>
        /// <param name="value">保存的值</param>
        /// <param name="expiry">过期时间</param>
        /// <returns></returns>
        public bool Set<T>(string key, T value, TimeSpan? expiry = default(TimeSpan?))
        {
            key = GetKeySuffix(key);
            if (expiry == null || expiry.HasValue == false)
            {
                return cache.Set(key, (value));
            }
            else
            {
                return cache.Set(key, (value), (int)expiry.Value.TotalSeconds);
            }
        }
        public T Get<T>(string key)
        {
            key = GetKeySuffix(key);
            return cache.Get<T>(key);
        }
        public T Get<T>(string key, Func<T> cachePopulate, TimeSpan? expiresTime, Func<T, bool> checkPopulate = null)
        {
            var cacheItem = Get<T>(key);
            if (!cacheItem.IsNullOrEmpty())
            {
                return cacheItem;
            }
            else if (cachePopulate != null)//调用fucloadData获取数据
            {
                var val = cachePopulate();
                if (!val.IsNullOrEmpty())//缓存
                {
                    if (checkPopulate == null || checkPopulate?.Invoke(val) == true)
                        Set(key, val, expiresTime);
                    return val;
                }
            }
            return default(T);
        }
        // <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryGet<T>(string key, out T val)
        {
            val = this.Get<T>(key);
            return !val.IsNullOrEmpty();
        }

        public bool Exists(string key)
        {
            key = GetKeySuffix(key);
            return cache.Exists(key);
        }
        public bool Remove(string key)
        {
            key = GetKeySuffix(key);
            return cache.Del(key) > 0;
        }
        /// <summary>
        /// 删除多个key
        /// </summary>
        /// <param name="keys">rediskey</param>
        /// <returns>成功删除的个数</returns>
        public long Remove(List<string> keys)
        {
            List<string> newKeys = keys.Select(GetKeySuffix).ToList();
            return cache.Del(newKeys.ToArray());
        }
        /// <summary>
        /// 删除所有缓存
        /// </summary>
        public bool RemoveAll()
        {
            var keys = cache.Keys("*");
            return cache.Del(keys) > 0;
        }
        #endregion

        #region Hash
        /// <summary>
        /// 判断某个数据是否已经被缓存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="fieldKey"></param>
        /// <returns></returns>
        public bool HashExists(string key, string fieldKey)
        {
            key = GetKeySuffix(key);
            return cache.HExists(key, fieldKey);
        }
        /// <summary>
        /// 移除hash中的某值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="fieldKey"></param>
        /// <returns></returns>
        public bool RemoveHash(string key, string fieldKey)
        {
            key = GetKeySuffix(key);
            return cache.HDel(key, fieldKey) > 0;
        }

        public bool HashSet<T>(string key, string fieldKey, T fieldValue)
        {
            key = GetKeySuffix(key);
            return cache.HSet(key, fieldKey, fieldValue);
        }


        /// <summary>
        /// 从hash表获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dataKey"></param>
        /// <returns></returns>
        public T HashGet<T>(string key, string dataKey)
        {
            key = GetKeySuffix(key);
            var value = cache.HGet<T>(key, dataKey);
            return (value);
        }
        /// <summary>
        /// 获取dataKeys所有Value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public List<T> HashGet<T>(string key, List<string> dataKeys)
        {
            key = GetKeySuffix(key);
            var values = cache.HMGet<T>(key, dataKeys.ToArray());
            return values?.ToList();
        }

        /// <summary>
        /// 获取hashkey所有Redis Value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public Dictionary<string, T> HashGetAll<T>(string key)
        {
            key = GetKeySuffix(key);
            var values = cache.HGetAll<T>(key);
            return values;
        }

        /// <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dataKey"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGet<T>(string key, string dataKey, out T val)
        {
            val = this.HashGet<T>(key, dataKey);
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dataKey"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGet<T>(string key, List<string> dataKey, out List<T> val)
        {
            val = default(List<T>);
            if (dataKey?.Count > 0)
            {
                val = HashGet<T>(key, dataKey);
            }
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 获取hashkey所有Redis key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGetAll<T>(string key, out Dictionary<string, T> val)
        {
            val = HashGetAll<T>(key);
            if (!val.IsNullOrEmpty()) return true;
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 获取hashkey所有cache key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGetAll<T>(string key, out List<T> val)
        {
            val = default(List<T>);
            if (this.TryHashGetAll<T>(key, out Dictionary<string, T> dicval) == true)
            {
                val = dicval.Values.ToList();
            }
            return !val.IsNullOrEmpty();
        }

        /// <summary>
        /// 批量删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="listdataKey"></param>
        /// <returns></returns>
        public bool HashDeleteExecute(string key, List<string> listdataKey)
        {
            if (listdataKey == null || listdataKey.Count == 0) return false;
            key = GetKeySuffix(key);

            return cache.HDel(key, listdataKey.ToArray()) > 0;
        }
        /// <summary>
        /// 批量删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="listdataKey"></param>
        /// <returns></returns>
        public bool HashDeleteExecute(Dictionary<string, List<string>> listdataKey)
        {
            if (listdataKey == null || listdataKey.Count == 0) return false;
            foreach (var dic in listdataKey)
            {
                var ret = HashDeleteExecute(dic.Key, dic.Value);
            }
            return true;
        }
        /// <summary>
        /// 批量更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="DellistdataKey">批量删除</param>
        /// <param name="dicHashEntrys">批量保存</param>
        /// <returns></returns>
        public bool HashUpdateExecute<T>(Dictionary<string, List<string>> DellistdataKey, Dictionary<string, Dictionary<string, T>> dicHashEntrys)
        {
            if (DellistdataKey == null && DellistdataKey.Count == 0 && dicHashEntrys == null && dicHashEntrys.Count == 0) return false;

            if (DellistdataKey?.Count > 0)//删除
            {
                HashDeleteExecute(DellistdataKey);
            }
            if (dicHashEntrys?.Count > 0)//更新
            {
                return HashSetExecute(dicHashEntrys);
            }
            return true;
        }
        /// <summary>
        /// 批量保存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dicHashEntrys"></param>
        /// <returns></returns>
        public bool HashSetExecute<T>(string key, Dictionary<string, T> dicHashEntrys)
        {
            if (dicHashEntrys == null || dicHashEntrys.Count == 0) return false;
            key = GetKeySuffix(key);
            List<object> list = new List<object>();
            foreach (var dic in dicHashEntrys)
            {
                list.Add(dic.Key);
                list.Add(dic.Value);
            }
            return cache.HMSet(key, list.ToArray());
        }
        /// <summary>
        /// 批量保存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dicHashEntrys"></param>
        /// <returns></returns>
        public bool HashSetExecute<T>(Dictionary<string, Dictionary<string, T>> dicHashEntrys)
        {
            if (dicHashEntrys == null || dicHashEntrys.Count == 0) return false;
            foreach (var dic in dicHashEntrys)
            {
                var ret = HashSetExecute(dic.Key, dic.Value);
            }
            return true;
        }
        #endregion

        #region LockTake
        CSRedisClientLock ClientLock = null;
        public bool LockRelease(string key, string value)
        {
            var rets = ClientLock?.Unlock() == true;
            return rets;
        }
        public bool LockTake(string key, string value, TimeSpan? expiry)
        {
            key = GetKeySuffix(key);
            var lockName = key + "_lock"; //unique lock name. key-relative.
            ClientLock = cache.Lock(lockName, (expiry ?? TimeSpan.FromSeconds(10)).Seconds);
            return ClientLock.IsNotNullOrEmpty();
        }

        public IDisposable AcquireLock(string key, TimeSpan timeOut, bool useLock = true)
        {
            return new CacheLock(this, key, timeOut, useLock);
        }
        #endregion

        #region 辅助方法
        /// <summary>
        /// 设置前缀
        /// </summary>
        /// <param name="customKey"></param>
        public void SetSysCustomKey(string customKey)
        {
            KeySuffix = customKey;
        }

        #region GetManager
        private CSRedisClient GetRedisClient(string redisConnectionString = null, int minSize = 100, int maxSize = 100)
        {
            try
            {
                redisConnectionString = redisConnectionString ?? RedisConnectionString;

                var key = $"{DBName}{DbIndex}";
                if (!_pool.ContainsKey(key) || cache == null)
                {
                    var objectPool = new Lazy<ObjectPool<CSRedisClient>>(() => new ObjectPool<CSRedisClient>(() =>
                    {
                        var connection = GetManager(redisConnectionString);
                        return connection;
                    }, minSize, maxSize));
                    _pool.GetOrAdd(key, objectPool);

                    cache = objectPool.Value.GetObject();
                    RedisHelper.Initialization(cache);
                    return cache;
                }
                else
                {
                    cache = cache = _pool[key].Value.GetObject();
                    RedisHelper.Initialization(cache);
                    return cache;
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        private CSRedisClient GetManager(string redisConnectionString = null)
        {
            redisConnectionString = redisConnectionString ?? RedisConnectionString;
            var connect = new CSRedisClient(redisConnectionString + ",defaultDatabase=" + DbIndex);

            return connect;
        }
        #endregion

        private string GetKeySuffix(string key)
        {
            return string.IsNullOrEmpty(KeySuffix) ? key : string.Format("_{0}_{1}", KeySuffix, key);
        }

        public void Dispose()
        {
            //if (connection != null)
            //{
            //    connection.Close();
            //}
            GC.SuppressFinalize(this);
        }
        #endregion

    }
}
